#include "libPFS.h"

int32_t obtenerInterfazCache (char  **interfaz)
{
  char pTiempo [18];
  int32_t tamInterfaz;
  extern stBootSector sectorBoot;
  time_t tiempo = time (NULL);
  struct tm *tlocal = localtime (&tiempo);
  char separadores [] = "-----------------------------";
  char timeStamp [] = "TimeStamp:";
  char tamanio [] = "Tamanio de Bloque de Cache:";

  int8_t camposAdministrativos = 3;
  strftime (pTiempo, 18, "%d/%m/%y %H:%M:%S", tlocal);
  tamInterfaz = sizeof (pTiempo) + sizeof (separadores) + sizeof (timeStamp)
      + sizeof (tamanio) + camposAdministrativos;
  *interfaz = ( char *) calloc (tamInterfaz, sizeof(char));
  sprintf (*interfaz,"\n%s\n%s%s\n%s%d KB\n", separadores, timeStamp, pTiempo,
      tamanio, sectorBoot.BPB_SecPerClus * TAMSECTOR);
  return tamInterfaz;
}

int32_t obtenerNombreArchivo (char  **nombreCompleto, char *nombre)
{
  int32_t nombreArchivo;
  extern int32_t cantClusterCache;
  char cadena [] = "Archivo:";
  char cantidad [] = "Cantidad De Bloques De Cache:";
  nombreArchivo = sizeof (cadena) + strlen (nombre) + sizeof (cantidad)
      + sizeof(int32_t) + 1;
  *nombreCompleto = ( char *) malloc (sizeof(char) * nombreArchivo);
  sprintf (*nombreCompleto,"%s%s\n%s%d\n", cadena, nombre, cantidad,
      cantClusterCache);
  return nombreArchivo;
}

int32_t contenidoBloque (char **contenido, int32_t nroBloque)
{
  char content [] = "Contenido de Bloque De Cache:";
  int32_t sizeContenido;
  sizeContenido = sizeof (content) + sizeof(int32_t) + 1;
  *contenido = ( char *) malloc (sizeof(char) *  sizeContenido);
  sprintf (*contenido,"%s%d\n", content, nroBloque);
  return sizeContenido;
}
void cache_dump ()
{
  FILE *cacheDump = fopen ("cache_dump.txt", "ab");
  char *interfaz, *nombre, *contenido;

  int32_t tamInterfaz, posicion = 0, tamCluster, largoNombre = 0;
  extern stBootSector sectorBoot;
  extern t_listaArchivos *listaArchivos;
  int32_t sizeContenido;
  extern int32_t cantClusterCache;
  tamInterfaz = obtenerInterfazCache (&interfaz);
  t_nodoArchivos *aux = listaArchivos->cabeza;
  tamCluster = sectorBoot.BPB_SecPerClus * TAMSECTOR;

  fwrite (interfaz, tamInterfaz, sizeof(char), cacheDump);
  while (aux != NULL)
    {
      largoNombre = obtenerNombreArchivo (&nombre, aux->datos.nombre);
      fwrite (nombre, largoNombre, sizeof(char), cacheDump);
      free(nombre);
      while (aux->datos.dataCache [posicion].nroCluster != 0
          && posicion < cantClusterCache)
        {
          sizeContenido = contenidoBloque (&contenido, posicion);
          fwrite (contenido, sizeContenido, sizeof(char), cacheDump);
          fwrite (aux->datos.dataCache[posicion].cluster,tamCluster, sizeof(char), cacheDump);
          free(contenido);
          posicion++;
          if (posicion == cantClusterCache)
            break;
        }
      aux = aux->next;
    }
  fclose (cacheDump);
  free (interfaz);
  return;
}



t_listaArchivos *crearLista ()
{
  t_listaArchivos *lista = malloc (sizeof(t_listaArchivos));
  pthread_mutex_init(&lista->lista, NULL);
  lista->cabeza = NULL;
  lista->cola = NULL;
  return lista;
}
int32_t obtenerCantidad ()
{
  extern stBootSector sectorBoot;
  extern t_config archivoCfg;
  int32_t cantidad;
  cantidad = archivoCfg.tamCache;
  return cantidad / (sectorBoot.BPB_SecPerClus * TAMSECTOR);
}
void agregarEnLaLista (uint32_t clusterInicial, uint32_t fileSize, char *nombre)
{
  extern t_listaArchivos *listaArchivos;
  extern int32_t cantClusterCache;
  extern stBootSector sectorBoot;
  extern t_config archivoCfg;
  t_nodoArchivos *insertarNodo = NULL;
  int32_t tamCluster = sectorBoot.BPB_SecPerClus * TAMSECTOR,recorroCant=0;
  insertarNodo = ( t_nodoArchivos *) calloc (1, sizeof(t_nodoArchivos));

  insertarNodo->next = NULL;
  insertarNodo->datos.clusterInicial = clusterInicial;
  insertarNodo->datos.tamArchivo = fileSize;
  insertarNodo->datos.nombre = (char *)calloc(strlen(nombre) + 1,sizeof(char));
  memcpy (insertarNodo->datos.nombre, nombre,strlen(nombre));

  pthread_mutex_init(&insertarNodo->datos.mutexNodo,NULL);
  if (archivoCfg.tamCache > 0)
    {
     insertarNodo->datos.dataCache = (stCache *)calloc(cantClusterCache,sizeof(stCache));
     while (recorroCant < cantClusterCache)
       {
         insertarNodo->datos.dataCache[recorroCant].cluster = (char *)calloc(1,tamCluster);
         recorroCant++;
       }
    }
  insertarNodo->datos.cantidadAperturas = 1;

  pthread_mutex_lock(&listaArchivos->lista);
  if (listaArchivos->cabeza == NULL)
    {
      listaArchivos->cabeza = insertarNodo;
      listaArchivos->cola = insertarNodo;
    }
  else
    {
      listaArchivos->cola->next = insertarNodo;
      listaArchivos->cola = insertarNodo;
    }
  pthread_mutex_unlock(&listaArchivos->lista);
}
void agregarApertura (char *nombre)
{
  extern t_listaArchivos *listaArchivos;
  t_nodoArchivos *recorroLista;
  recorroLista = listaArchivos->cabeza;

  while (recorroLista != NULL)
    {
      if (strcmp(recorroLista->datos.nombre,nombre) == 0)
        {
          pthread_mutex_lock(&listaArchivos->lista);
          recorroLista->datos.cantidadAperturas += 1;
          pthread_mutex_unlock(&listaArchivos->lista);
          return;
        }
     recorroLista = recorroLista->next;
    }
}
t_nodoArchivos *buscarEnLaLista (char *nombre)
{
  extern t_listaArchivos *listaArchivos;
  t_nodoArchivos *recorroLista = NULL;
  pthread_mutex_lock(&listaArchivos->lista);
  recorroLista = listaArchivos->cabeza;
  pthread_mutex_unlock(&listaArchivos->lista);
  while (recorroLista != NULL)
    {
      if (strncmp(recorroLista->datos.nombre,nombre,strlen(nombre)) == 0 )
        {
          return recorroLista;
        }
      recorroLista = recorroLista->next;
    }
  return recorroLista;
}

int32_t buscarCluster (t_nodoArchivos *nodoArchivo, uint32_t clusterInicial)
{
  t_nodoArchivos *aux = nodoArchivo;
  extern int32_t cantClusterCache;
  int32_t posicion,lugarEnCache=0;

  for(posicion=1;posicion < cantClusterCache && aux->datos.dataCache[lugarEnCache].nroCluster != clusterInicial;posicion++,lugarEnCache++);

  if(posicion == cantClusterCache && aux->datos.dataCache[lugarEnCache].nroCluster != clusterInicial)
    return -1;

return lugarEnCache;
}

int32_t agregarCluster(t_nodoArchivos *nodoArchivo, uint32_t clusterInicial,const char *dataCluster,size_t size)

{
  extern int32_t cantClusterCache;
  extern stBootSector sectorBoot;
  t_nodoArchivos aux = *nodoArchivo;
  int32_t tamCluster = sectorBoot.BPB_SecPerClus * TAMSECTOR;
  int32_t posicion,lugarEnCache=0;

  for (posicion = 1;aux.datos.dataCache[lugarEnCache].nroCluster != 0 && posicion < cantClusterCache;posicion++,lugarEnCache++);

  if(posicion == cantClusterCache && aux.datos.dataCache[lugarEnCache].nroCluster != 0)
    {
      return -1; // Aplico Algoritmo de Reemplazo
    }
  pthread_mutex_lock(&nodoArchivo->datos.mutexNodo);

  if (size < tamCluster)
      memcpy(nodoArchivo->datos.dataCache[lugarEnCache].cluster,dataCluster,size);
    else
      memcpy(nodoArchivo->datos.dataCache[lugarEnCache].cluster,dataCluster,tamCluster);

  nodoArchivo->datos.dataCache[lugarEnCache].modificado = 0;
  nodoArchivo->datos.dataCache[lugarEnCache].nroCluster = clusterInicial;
  nodoArchivo->datos.dataCache[lugarEnCache].ultimoAcceso = time(NULL);
  pthread_mutex_unlock(&nodoArchivo->datos.mutexNodo);

return lugarEnCache;
}

int32_t agregarClusterEscritura(t_nodoArchivos *nodoArchivo, uint32_t clusterInicial,const char *dataCluster,size_t size)
{
  extern int32_t cantClusterCache;
  extern stBootSector sectorBoot;
  t_nodoArchivos aux = *nodoArchivo;
  int32_t tamCluster = sectorBoot.BPB_SecPerClus * TAMSECTOR;
  int32_t posicion,lugarEnCache=0;

    for (posicion = 1;aux.datos.dataCache[lugarEnCache].nroCluster != 0 && posicion < cantClusterCache && aux.datos.dataCache[lugarEnCache].modificado == 1;posicion++,lugarEnCache++);

    if(posicion == cantClusterCache && aux.datos.dataCache[lugarEnCache].nroCluster != 0 && aux.datos.dataCache[lugarEnCache].modificado == 1)
      {
        return -1; // Todos Los Cacheados Son de Escritura, Tengo que hacer un flush
      }
    pthread_mutex_lock(&nodoArchivo->datos.mutexNodo);

    if (size < tamCluster)
        memcpy(nodoArchivo->datos.dataCache[lugarEnCache].cluster,dataCluster,size);
      else
        memcpy(nodoArchivo->datos.dataCache[lugarEnCache].cluster,dataCluster,tamCluster);

    nodoArchivo->datos.dataCache[lugarEnCache].modificado = 1;
    nodoArchivo->datos.dataCache[lugarEnCache].nroCluster = clusterInicial;
    nodoArchivo->datos.dataCache[lugarEnCache].ultimoAcceso = time(NULL);
    pthread_mutex_unlock(&nodoArchivo->datos.mutexNodo);

  return lugarEnCache;
}
int32_t modificarCluster(t_nodoArchivos *nodoArchivo, uint32_t clusterInicial,const char *dataCluster,size_t size,uint32_t offset)
{
  uint32_t deClusABloque, primerSecBloque;
  char *cluster;
  extern int32_t cantClusterCache;
  extern stBootSector sectorBoot;
  t_nodoArchivos aux = *nodoArchivo;
  int32_t tamCluster = sectorBoot.BPB_SecPerClus * TAMSECTOR;
  int32_t posicion,lugarEnCache=0;

  for (posicion = 1;aux.datos.dataCache[lugarEnCache].nroCluster != 0 && posicion < cantClusterCache;posicion++,lugarEnCache++);

  if(posicion == cantClusterCache && aux.datos.dataCache[lugarEnCache].nroCluster != 0)
    {
      return -1; // Aplico Algoritmo de Reemplazo
    }
  pthread_mutex_lock(&nodoArchivo->datos.mutexNodo);
  deClusABloque = deClusterABloque (clusterInicial);
  primerSecBloque = obtenerPrimerSectorDeUnBloque (deClusABloque);
  armarCluster (primerSecBloque,NULL,&cluster);
  memcpy(nodoArchivo->datos.dataCache[lugarEnCache].cluster,cluster,tamCluster);

  if (size < tamCluster)
      memcpy(nodoArchivo->datos.dataCache[lugarEnCache].cluster + offset ,dataCluster,size);
    else
      memcpy(nodoArchivo->datos.dataCache[lugarEnCache].cluster + offset ,dataCluster,tamCluster);
  nodoArchivo->datos.dataCache[lugarEnCache].nroCluster = clusterInicial;
  nodoArchivo->datos.dataCache[lugarEnCache].ultimoAcceso = time(NULL);
  pthread_mutex_unlock(&nodoArchivo->datos.mutexNodo);

  liberarMemoria(&cluster);

return lugarEnCache;
}

void quitarNodo(t_nodoArchivos *nodoArchivo)
{
  extern t_listaArchivos *listaArchivos;
  extern int32_t cantClusterCache;
  extern t_config archivoCfg;
  t_nodoArchivos *recorroLista = NULL,*nodoAnterior;
  uint8_t bool = 0,recorroCluster=0;
  pthread_mutex_lock(&listaArchivos->lista);
  recorroLista = listaArchivos->cabeza;
  nodoAnterior = listaArchivos->cabeza;

  pthread_mutex_lock(&nodoArchivo->datos.mutexNodo);
  while (recorroLista != NULL && bool == 0)
    {
    if (strcmp(recorroLista->datos.nombre,nodoArchivo->datos.nombre) == 0 )
        {
        bool = 1;
        if (nodoAnterior != recorroLista)
            nodoAnterior->next = recorroLista->next;
        else
          {
            listaArchivos->cabeza = recorroLista->next;
            listaArchivos->cola = recorroLista->next;
          }
        pthread_mutex_unlock(&listaArchivos->lista);
        }
        nodoAnterior = recorroLista;
        recorroLista = recorroLista->next;
    }

  free(nodoAnterior->datos.nombre);
  if (archivoCfg.tamCache > 0)
    {
      while(recorroCluster < cantClusterCache)
        {
        free(nodoAnterior->datos.dataCache[recorroCluster].cluster);
        recorroCluster++;
        }
      free(nodoAnterior->datos.dataCache);

    }
  pthread_mutex_unlock(&nodoArchivo->datos.mutexNodo);
}

int32_t algoritmoReemplazoLRU(t_nodoArchivos *nodoArchivo,uint32_t clusterInicial,const char *dataCluster,size_t size)
{
  extern int32_t cantClusterCache;
  int32_t posicion = 0;
  int32_t lugarEnCache = 0,cacheMiss=0;
  extern stBootSector sectorBoot;
  t_nodoArchivos nodoAuxiliar = *nodoArchivo;
  time_t tiempo = nodoAuxiliar.datos.dataCache->ultimoAcceso;
  int32_t tamCluster = sectorBoot.BPB_SecPerClus * TAMSECTOR;
  for (posicion=1; posicion < cantClusterCache ; posicion++,lugarEnCache++)
    {
      if (nodoAuxiliar.datos.dataCache[lugarEnCache].ultimoAcceso < tiempo)
      {
          tiempo = nodoAuxiliar.datos.dataCache[lugarEnCache].ultimoAcceso;
          cacheMiss = lugarEnCache;
      }
    }

  pthread_mutex_lock(&nodoArchivo->datos.mutexNodo);
  if (nodoArchivo->datos.dataCache[cacheMiss].modificado == 1) // Si Estaba Modificado Lo mando a Escribir a Disco Antes de Reemplazarlo
    escribeArchivos(nodoArchivo->datos.dataCache [cacheMiss].nroCluster ,nodoArchivo->datos.dataCache [cacheMiss].cluster);

  nodoArchivo->datos.dataCache[cacheMiss].nroCluster = clusterInicial;
  nodoArchivo->datos.dataCache[cacheMiss].modificado = 0;
  nodoArchivo->datos.dataCache[cacheMiss].ultimoAcceso = time(NULL);

  if (size < tamCluster)
    memcpy(nodoArchivo->datos.dataCache[cacheMiss].cluster,dataCluster,size);
  else
    memcpy(nodoArchivo->datos.dataCache[cacheMiss].cluster,dataCluster,tamCluster);

  pthread_mutex_unlock(&nodoArchivo->datos.mutexNodo);

  return cacheMiss;
}

